package com.ityuwen.mengyagushi.account.service.impl;

import com.ityuwen.mengyagushi.account.mapper.RechargeInfoMapper;
import com.ityuwen.mengyagushi.account.mapper.UserAccountDetailMapper;
import com.ityuwen.mengyagushi.account.mapper.UserAccountMapper;
import com.ityuwen.mengyagushi.account.service.UserAccountService;
import com.ityuwen.mengyagushi.account.util.DelayTaskClient;
import com.ityuwen.mengyagushi.common.constant.KafkaConstant;
import com.ityuwen.mengyagushi.common.constant.RedisConstant;
import com.ityuwen.mengyagushi.common.constant.SystemConstant;
import com.ityuwen.mengyagushi.common.execption.GuiguException;
import com.ityuwen.mengyagushi.common.result.ResultCodeEnum;
import com.ityuwen.mengyagushi.common.service.KafkaService;
import com.ityuwen.mengyagushi.common.util.AuthContextHolder;
import com.ityuwen.mengyagushi.model.account.RechargeInfo;
import com.ityuwen.mengyagushi.model.account.UserAccount;
import com.ityuwen.mengyagushi.model.account.UserAccountDetail;
import com.ityuwen.mengyagushi.vo.account.AccountLockResultVo;
import com.ityuwen.mengyagushi.vo.account.AccountLockVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.concurrent.TimeUnit;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class UserAccountServiceImpl extends ServiceImpl<UserAccountMapper, UserAccount> implements UserAccountService {

	@Autowired
	private UserAccountMapper userAccountMapper;

	@Autowired
	private RedisTemplate redisTemplate;

	@Autowired
	private UserAccountDetailMapper detailMapper;

	@Autowired
	private RechargeInfoMapper rechargeInfoMapper;

	@Autowired
	private DelayTaskClient delayTaskClient;

	@Autowired
	private KafkaService kafkaService;

	@Transactional
	@Override
	public void saveUserAccount(Long userId) {
		UserAccount userAccount = new UserAccount();
		userAccount.setUserId(userId);
		this.save(userAccount);
//		int i = 1/0;
	}

	@Transactional
	@Override
	public AccountLockResultVo checkLock(AccountLockVo accountLockVo) {

		AccountLockResultVo resultVo = null;
		String orderNo = accountLockVo.getOrderNo();
		String key = RedisConstant.ACCOUNT_LOCK_PREFIX + orderNo;
		String idemKey = RedisConstant.ACCOUNT_IDEM_PREFIX + orderNo;

		// 幂等性保证：防止重复锁余额，同一个订单只能锁定一次
		Boolean flag = this.redisTemplate.opsForValue().setIfAbsent(idemKey, "", RedisConstant.CACHE_TIMEOUT, TimeUnit.SECONDS);
		if (!flag){
			// 如果重复锁定，查询锁定信息，判断锁定信息是否为空，如果不为空则直接返回锁定结果
			resultVo = (AccountLockResultVo)this.redisTemplate.opsForValue().get(key);
			if (resultVo != null){
				return resultVo;
			} else {
				throw new GuiguException(ResultCodeEnum.ACCOUNT_LOCK_ERROR);
			}
		}

		try {
			// 验余额
			UserAccount userAccount = this.userAccountMapper.check(accountLockVo.getUserId(), accountLockVo.getAmount());
			if (userAccount == null) {
				// 判断验余额的结果，如果为null则说明余额不足
				return null;
			}

			// 锁定余额
			if (this.userAccountMapper.lock(userAccount.getId(), accountLockVo.getAmount()) == 1) {
				// 记录账户变化明细
				UserAccountDetail accountDetail = new UserAccountDetail();
				BeanUtils.copyProperties(accountLockVo, accountDetail);
				accountDetail.setTitle("账户锁定：" + accountLockVo.getContent());
				accountDetail.setTradeType(SystemConstant.ACCOUNT_TRADE_TYPE_LOCK);
				this.detailMapper.insert(accountDetail);

				// 缓存锁定结果到redis缓存
				resultVo = new AccountLockResultVo();
				BeanUtils.copyProperties(accountLockVo, resultVo);
				this.redisTemplate.opsForValue().set(key, resultVo, RedisConstant.CACHE_TIMEOUT, TimeUnit.SECONDS);
			}

			// 这里无法判断未来是否会创建订单（也无法预知未来会不会扣减余额和解锁余额）发送延时消息，定时解锁余额
			this.delayTaskClient.getDelayTask().sendDelayMsg(orderNo, 15, TimeUnit.MINUTES);


			return resultVo;
		} catch (BeansException e) {
			// 把幂等性的key给删除，将来可以进行代码的重试
			this.redisTemplate.delete(idemKey);
			throw new RuntimeException(e);
		}
	}

	@Transactional
	@Override
	public void minus(String orderNo) {

		String key = RedisConstant.ACCOUNT_LOCK_PREFIX + orderNo;

		// 防止重复扣减余额：一个订单只能扣减一次
		String idemKey = RedisConstant.ACCOUNT_MINUS_PREFIX + orderNo;
		Boolean flag = this.redisTemplate.opsForValue().setIfAbsent(idemKey, "", RedisConstant.CACHE_TIMEOUT, TimeUnit.SECONDS);
		if (!flag){
			return;
		}

		try {
			// 根据订单编号查询余额锁定信息
			AccountLockResultVo lockResultVo = (AccountLockResultVo)this.redisTemplate.opsForValue().get(key);
			if (lockResultVo == null){
				return;
			}

			// 扣减余额
			if (this.userAccountMapper.minus(lockResultVo.getUserId(), lockResultVo.getAmount()) == 1) {
				// 记录账户变化明细
				UserAccountDetail accountDetail = new UserAccountDetail();
				BeanUtils.copyProperties(lockResultVo, accountDetail);
				accountDetail.setTitle("账户扣减：" + lockResultVo.getContent());
				accountDetail.setTradeType(SystemConstant.ACCOUNT_TRADE_TYPE_MINUS);
				accountDetail.setOrderNo(orderNo);
				this.detailMapper.insert(accountDetail);

				// 删除redis中的锁定信息缓存
				this.redisTemplate.delete(key);

				// 发送kafka消息，更新订单状态
				this.kafkaService.sendMsg(KafkaConstant.QUEUE_ORDER_PAY_SUCCESS, orderNo);
			}
		} catch (BeansException e) {
			this.redisTemplate.delete(idemKey);
			throw new RuntimeException(e);
		}
	}

	@Transactional
	@Override
	public void unlock(String orderNo) {

		String key = RedisConstant.ACCOUNT_LOCK_PREFIX + orderNo;

		// 防止重复扣减余额：一个订单只能扣减一次
		String idemKey = RedisConstant.ACCOUNT_MINUS_PREFIX + orderNo;
		Boolean flag = this.redisTemplate.opsForValue().setIfAbsent(idemKey, "", RedisConstant.CACHE_TIMEOUT, TimeUnit.SECONDS);
		if (!flag){
			return;
		}

		try {
			// 根据订单编号查询余额锁定信息
			AccountLockResultVo lockResultVo = (AccountLockResultVo)this.redisTemplate.opsForValue().get(key);
			if (lockResultVo == null){
				return;
			}

			// 解锁余额
			if (this.userAccountMapper.unlock(lockResultVo.getUserId(), lockResultVo.getAmount()) == 1) {
				// 记录账户变化明细
				UserAccountDetail accountDetail = new UserAccountDetail();
				BeanUtils.copyProperties(lockResultVo, accountDetail);
				accountDetail.setTitle("解锁余额：" + lockResultVo.getContent());
				accountDetail.setTradeType(SystemConstant.ACCOUNT_TRADE_TYPE_UNLOCK);
				accountDetail.setOrderNo(orderNo);
				this.detailMapper.insert(accountDetail);

				// 删除redis中的锁定信息缓存
				this.redisTemplate.delete(key);
			}
		} catch (BeansException e) {
			this.redisTemplate.delete(idemKey);
			throw new RuntimeException(e);
		}
	}

	@Override
	public void paySuccess(String orderNo) {
		// 更新充值记录为已支付状态
		RechargeInfo rechargeInfo = this.rechargeInfoMapper.selectOne(new LambdaQueryWrapper<RechargeInfo>().eq(RechargeInfo::getOrderNo, orderNo));
		rechargeInfo.setRechargeStatus(SystemConstant.ORDER_STATUS_PAID);
		if (this.rechargeInfoMapper.updateById(rechargeInfo) == 1) {
			// 给账户加钱
			this.userAccountMapper.addAmount(rechargeInfo.getUserId(), rechargeInfo.getRechargeAmount());

			// 记录账户变化明细
			UserAccountDetail accountDetail = new UserAccountDetail();
			accountDetail.setAmount(rechargeInfo.getRechargeAmount());
			accountDetail.setUserId(rechargeInfo.getUserId());
			accountDetail.setTitle("账户充值");
			accountDetail.setTradeType(SystemConstant.ACCOUNT_TRADE_TYPE_DEPOSIT);
			accountDetail.setOrderNo(orderNo);
			this.detailMapper.insert(accountDetail);
		}
	}

	@Override
	public Page<UserAccountDetail> findUserAccountDetials(Integer page, Integer limit, String type) {
		return this.detailMapper.selectPage(new Page<>(page, limit), new LambdaQueryWrapper<UserAccountDetail>().eq(UserAccountDetail::getUserId, AuthContextHolder.getUserId()).eq(UserAccountDetail::getTradeType, type));
	}

//	public static void main(String[] args) {
//		System.out.println("发送时间：" + System.currentTimeMillis());
//		new DelayTask().sendMsg("1232132131", 10, TimeUnit.SECONDS);
//		System.out.println("==================");
//	}
}

